import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;

public class ReceiverThread extends Thread {

	ObjectInputStream objIn;
	PeerData peerData;
	int otherPeerID = 0;
	int requiredPieceNum = -1;
	SharedMessageQueue[] sharedMsg;
	CountDownLatch countDownLatch;
	Logger logger;
	FileHandler fileHandler;
	int pieceNum; // Piece for which request the request is sent and piece will
					// be received

	ReceiverThread(ObjectInputStream objIn, PeerData peerData,
			SharedMessageQueue[] sharedMsg, CountDownLatch countDown,
			int otherPeerID, Logger logger, FileHandler fileHandler, String name) {
		super(name);
		this.peerData = peerData;
		this.objIn = objIn;
		this.sharedMsg = sharedMsg;
		this.countDownLatch = countDown;
		this.otherPeerID = otherPeerID;
		this.logger = logger;
		this.fileHandler = fileHandler;
	}

	public void run() {
		Message rcvMsg;
		Byte type;
		Message sendMsg = null;
		Message haveMsg = null;
		Queue<Integer> requiredPieceNum = new LinkedList<Integer>();

		// Send handshake message initially
		sendMsg = new HandshakeMessage(peerData.getPeerID());
		sharedMsg[peerData.getPeerNum(otherPeerID)].enQueue(sendMsg);

		try {
			while ((rcvMsg = (Message) objIn.readObject()) != null) { // Receive
																		// a
																		// generic
																		// message
				System.out.println("peer: " + peerData.getPeerID()
						+ " received " + rcvMsg);
				type = rcvMsg.getType(); // Read type of received message

				if (type == MessageType.HANDSHAKE.getValue()) {
					// verify handshake message
					if (verifyHandshake(rcvMsg.getPayload())) {
						// Send Bitfield Message:
						sendMsg = new BitfieldMessage(
								peerData.hasCompleteFile(),
								peerData.getTotalNumOfPieces());
					} else {
						System.out.println("Incorrect handshake!");
						System.exit(0);
					}
				}

				else if (type == MessageType.BITFIELD.getValue()) {
					// CountDownLatch to put a barrier for initial sequence
					/*
					 * try { countDownLatch.await(); } catch
					 * (InterruptedException e) { // TODO Auto-generated catch
					 * block e.printStackTrace(); }
					 */

					// check Bit field and return true or false
					peerData.updateBitfield(otherPeerID, rcvMsg.getPayload());
					if (peerData.checkInterested(otherPeerID)) {
						// if true
						sendMsg = new InterestedMessage();
						peerData.addToInterestSend(otherPeerID);
					} else {
						// else
						sendMsg = new UninterestedMessage();
					}
				}

				else if (type == MessageType.INTERESTED.getValue()) {

					logger.log("Peer " + peerData.getPeerID() + " received the interested message from " + otherPeerID + ".");

					// add recvMsg to interestedNeighbours array.
					peerData.addToInterestReceived(otherPeerID);
					sendMsg = null;
				}

				else if (type == MessageType.UNINTERESTED.getValue()) {

					logger.log("Peer " + peerData.getPeerID() + " received the not interested message from " + otherPeerID + ".");

					// remove neighbour from unchokedNeighbours array if already
					// present. Remove from interestedNeighbours if already
					// present.
					peerData.removeFromInterestReceived(otherPeerID);
					sendMsg = null;

				}

				else if (type == MessageType.CHOKE.getValue()) {

					logger.log("Peer " + peerData.getPeerID()+ " is choked by " + otherPeerID + ".");

					// Dont send anymore req msg
					sendMsg = null;
				}

				else if (type == MessageType.UNCHOKE.getValue()) {

					logger.log("Peer " + peerData.getPeerID()+ " is unchoked by " + otherPeerID + ".");

					// Check neighbour bit field to see if it has the piece you
					// want
					if (peerData.checkInterested(otherPeerID)) {
						// if true
						int reqPiece = peerData
								.getInterestedPieceNumber(otherPeerID);
						if(reqPiece != -1){
						requiredPieceNum.add(reqPiece);
						sendMsg = new RequestMessage(reqPiece);
						}
						else
							sendMsg = null;
					} else {
						/************
						 * DOUBT : if dont require piece should we send
						 * uninterested msg or ignore?
						 ********/
						// else
						peerData.removeFromInterestSend(otherPeerID);
						sendMsg = new UninterestedMessage(); // Depends on above
																// clarification
					}
				}

				else if (type == MessageType.HAVE.getValue()) {

					

					// Update bit vector 2D array. Check if peer has pieces
					// required.
					int pieceNum = ((HaveMessage) rcvMsg).getPieceNumber();
					logger.log("Peer " + peerData.getPeerID()+ " received the have message from " + otherPeerID + " for the piece " + pieceNum + ".");
					peerData.updateBitfield(otherPeerID, pieceNum);
					if (peerData.checkInterested(otherPeerID)) {
						// if true
						sendMsg = new InterestedMessage();
						peerData.addToInterestSend(otherPeerID);
					} else {
						// else
						peerData.removeFromInterestSend(otherPeerID);
						sendMsg = new UninterestedMessage();
					}
				}

				else if (type == MessageType.REQUEST.getValue()) {
					pieceNum = ((RequestMessage) rcvMsg).getPieceNumber();
					System.out.println(pieceNum);
					sendMsg = new PieceMessage(pieceNum, fileHandler);
				}

				else if (type == MessageType.PIECE.getValue()) {
					// bit vector: If piece already received from other peer
					// ignore
					// else update bit field vector and send HAVE to everyone
					int pieceCount = 0;
					/*
					 * -1 implies that given piece is a duplicate piece
					 */
					int reqPieceRem = requiredPieceNum.remove();
					if ((pieceCount = peerData.updateBitfield(
							peerData.getPeerID(), reqPieceRem)) != -1) {
						logger.log("Peer " + peerData.getPeerID()+ " has downloaded the piece " + reqPieceRem	+ " from " + otherPeerID + ".Now the number of pieces it has is " + pieceCount + ".");

						// Write the piece to file
						fileHandler.setPiece(rcvMsg.getPayload(), reqPieceRem);
					}

					haveMsg = new HaveMessage(reqPieceRem);
					// check of you need to send not interested to some
					// neighbours

					// if more pieces required, pick random piece number from
					// required pieces
					int tempPeerID;
					for (int i = 0; i < peerData.getInterestedSend().length; i++) {
						if (peerData.getInterestedSend()[i]) {
							tempPeerID = peerData.getPeerID(i);
							if (!peerData.checkInterested(tempPeerID)) {
								peerData.removeFromInterestSend(tempPeerID);
								sendMsg = new UninterestedMessage(); // Depends
																		// on
																		// above
																		// clarification
							} else {
								if (tempPeerID == otherPeerID) {
									int reqPiece = peerData
											.getInterestedPieceNumber(otherPeerID);
									if(reqPiece != -1){
									requiredPieceNum.add(reqPiece);
									System.out
											.println("send req msg after receiving piece "
													+ i);
									sendMsg = new RequestMessage(reqPiece);
									}
									else
										sendMsg = null;
								}
							}
							if (sendMsg != null) {
								sharedMsg[i].enQueue(sendMsg);
								synchronized (sharedMsg[i].dummyObject) {
									sharedMsg[i].dummyObject.notify();
								}
								sendMsg = null;
							}
						}
					}
					// requiredPieceNum = -1;//reseting required piece number
				}

				else {
					// Unidentified Messge type: LOG error
					System.out.println("Unindentified Msg : " + rcvMsg
							+ "type :" + type + " msg type :"
							+ MessageType.UNCHOKE.getValue() + " "
							+ (type == MessageType.UNCHOKE.getValue()));
					sendMsg = null;
				}

				if (sendMsg != null) {
					System.out.println("peer: " + peerData.getPeerID()
							+ " sending " + sendMsg);
					sharedMsg[peerData.getPeerNum(otherPeerID)]
							.enQueue(sendMsg);
					synchronized (sharedMsg[peerData.getPeerNum(otherPeerID)].dummyObject) {
						sharedMsg[peerData.getPeerNum(otherPeerID)].dummyObject
								.notify();
					}
					sendMsg = null;
				}
				if (haveMsg != null) {
					// Send HAVE message to all peers!
					for (int count = 0; count < sharedMsg.length; count++) {
						if (count == peerData.getPeerNum(peerData.getPeerID())) {
							// can not send have message to self, since dont
							// have a shared msg queue for self
							continue;
						}

							sharedMsg[count].enQueue(haveMsg);
							synchronized (sharedMsg[count].dummyObject) {
								sharedMsg[count].dummyObject.notify();
							}
					}
					haveMsg = null;
				}
				// Program terminated when peer is no longer interested in any
				// and no one is interested in peer anymore.
				
				if (peerData.checkCloseConnection(otherPeerID)) {					
					break;
				}
			}
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	boolean verifyHandshake(byte[] payload) {
		HandShakeFormat hf = new HandShakeFormat();
		String check = "";
		int count = 0;
		for (int i = 0; i < 18; i++) {
			// check += (char)payload[count++];
		}
		System.out.println(check.toString());
		/*
		 * if(check.toString().compareTo(hf.header.toString()) == 0){ check =
		 * new byte[10]; for(int i=0;i<10;i++){ check[i] = payload[count++];
		 * if(check[i]!=hf.zeroBits[i]) return false; } check = new byte[4];
		 * for(int i=0;i<4;i++){ check[i] = payload[count++]; } if(otherPeerID
		 * == Integer.parseInt(check.toString())) return true; }
		 */
		return true;

	}
}
